プログラミング言語 Unison を知る
どうやって Unison を見つけたか
下の動画を見ていたら、2 つ知らない言語があった: Roc / Unison
https://www.youtube.com/watch?v=vhIQZ0px-Lc
https://gyazo.com/d0e5e023526806b14063ac16104a29bf
少し調べると Roc は他の FP とそこまで大差が無かった
一方、Unison は今までのプログラミング言語や開発スタイルを アンラーニング にする必要があるぐらい根本から違った
なので、とりあえず学んでみる
特徴
純粋関数型プログラミング言語
Haskell や Python に似た構文を持つ(オフサイドルール)
code:u(hs)
double : Nat -> Nat
double x =
x * 2
code:u(hs)
use List ++ +: :+
0, 1, 2, 3 ++ 4, 5
head = 1 +: 2, 3, 4
head :+ 5
型クラス は存在しない
代わりに Algebraic Effect(Ability)を採用しており、副作用 もこれで記述する
Ocaml や Eff、Koka などのプログラミング言語で採用されている機能
モナド の概念もない
評価戦略 として、基本的には 正格評価 だが、遅延評価(非正格評価)も可能
code:u(hs)
helloWorld : '{IO, Exception} () -- この ' が遅延評価を表す
helloWorld = do printLine "Hello World"
do はそれ以降を遅延計算するキーワード
Elixir のような パイプ演算子(|>)を持つ
code:u(hs)
Text.filter isDigit "abc_10203_def" |> Text.split ?0
code:u(hs)
Pattern.run (Pattern.capture (Pattern.many (chars "🍎🍏"))) "🍏🍎🍎🍏123"
code:u(hs)
Nat.range 0 10
|> List.map (x -> x Nat.* 100)
|> List.filter (const true)
|> List.foldLeft (Nat.+) 0
code:u(hs)
Optional.filter Nat.isEven <| Some 5
純粋関数型なので、依存している実装が変更されてない限りはテストがキャッシュされる
Go などでも採用されている
標準ライブラリ(lib/base)の機能が豊富
公式が提供しているライブラリも豊富(e.g. @unison/http)
テスティングフレームワーク も言語機能に含まれる
プロパティベーステスト も可能
コードベース(AST)は ハッシュ で管理する
例えば以下のような increment 関数は
code:u(hs)
increment : Nat -> Nat
increment x = x + 1
以下のように管理される
code:u(hs)
increment = (#arg1 -> #a8s6df921a8 #arg1 1)
コードベースを自身で直接管理することはできない(∵ ソースファイルは存在しない)
一時バッファである .u ファイルにコードを記述し、後述する ucm(Unison Codebase Management)を用いてコードベースに追加 する
コードの管理はすべて ucm を介して行う
もちろん Git / GitHub は使えないので ucm が代替する(後述)
変更管理や依存関係の解決、分散実行が容易、最低限の インクリメンタルビルド が可能になったりと様々なメリットがある
詳細は https://www.unison-lang.org/docs/the-big-idea/ を参照
サンプルコード
if / else
code:u(hs)
isEven num =
if mod num 2 === 0 then "even" else "odd"
パターンマッチ
code:u(hs)
isEven num = match num with
n | mod n 2 === 0 -> "even"
_ -> "odd"
シュガーシンタックス(cases)
code:u(hs)
isEven = cases
n | mod n 2 === 0 -> "even"
_ -> "odd"
code:u(hs)
match Some 12 with
Optional.None -> "none"
-- ガードパターン
Some n| Nat.isEven n -> "n is a variable and | is a pattern guard"
-- as パターン
opt@(Some n) -> "opt binds to the entire optional value"
型宣言
直和型
code:u(hs)
type LivingThings
= Animal | Plant | Fungi | Protists | Monera
直積型
code:u(hs)
type Pet = {
age : Nat,
species : Text,
foodPreferences : Text
}
例外処理
code:u(hs)
nonZero : Nat ->{Exception} Nat
nonZero =
use Nat ==
cases
n
| n == 0 -> Exception.raise (Generic.failure "Zero was found" n)
| otherwise -> n
code:u(hs)
catch do nonZero 0
副作用 のある処理
code:u(hs)
getRandomElem : a ->{Abort, Random} a
getRandomElem list =
use unison_base_4_8_0.abilities.Random natIn
randomIndex = natIn 0 (List.size list)
List.at! randomIndex list
code:u(hs)
toOptional! do splitmix 42 do getRandomElem 1, 2, 3
toOptional! と splitmix が Algebraic Effect Handlers
toOptional!: '{g Abort} a -> {g} Optional a
Abort ability を Optional に変換する
splitmix : Nat ->'{g, Random} t -> {g} t
受け取った Seed(乱数シード) を元に 乱数 を生成する
限定継続
Ability の定義
code:u(hs)
structural ability Abort where
abort : {Abort} a
code:u(hs)
structural ability Exception where
raise : Failure ->{Exception} x
structural は 構造的型付け で型定義を行うキーワード
通常は 名前的型付け
HTTP クライアント(@unison/http)
code:u(hs)
exampleGet : '{IO, Exception, Threads} HttpResponse
exampleGet _ =
use unison_base_4_8_0.IO.net.URI parse
uri = parse "https://share.unison-lang.org/@unison/httpclient"
req = do Http.get uri
Http.run req
code:u(hs)
printResponse : '{IO, Exception} ()
printResponse =
use unison_base_4_8_0.abilities Random.run
use systemfw_concurrent_7_2_1 Threads.run
do
Random.run do
Threads.run do
response = exampleGet ()
printLine (bodyText response)
ucm
Git 相当の操作や依存関係のインストール、コード検索など、あらゆる操作を行う対話型 CLI
Git + Lisp の REPL のイメージ
GitHub に相当するサービスとして Unison Share がある
Unison への Push 操作などもすべて ucm で行う
ucm を実行して立ち上げる
ucm コマンド例
help: ucm コマンド一覧
auth.login: ucm と Unison Share のアカウントを連携
プロジェクト関連
projects / project.list: プロジェクトの一覧
project.create: プロジェクトの作成
project.delete: プロジェクトの削除
switch: プロジェクトの切り替え
lib.install: サードパーティライブラリのインストール
ls: 名前空間を一覧表示する
コードベース関連
view: 指定された名前の定義確認`
idea.icon Unison Desktop を使えば GUI から閲覧可能
add / update: 一時バッファのコードをすべてコードベースに反映する
--- より下の行は無視される
delete: 指定した定義の削除
edit: 編集のため、一時バッファに指定された名前の定義をコピーする
reflog: 現在のブランチの変更差分を表示
find: コードベースで定義されている型・関数の検索
docs: コードドキュメント参照
ブランチ関連
branches / branch.list: ブランチの一覧
merge: ブランチのマージ
実行関連
run: 指定された {IO,Exception} () の関数を実行する(e.g. printLine を用いた関数)
compile: 指定された '{IO, Exception} () の関数をコンパイルして実行ファイル(.uc)ファイルを生成
生成したファイルは $ ucm run.compiled で実行可能
テスト関連
test: テスト実行
とりあえず動かしてみよう
https://www.unison-lang.org/docs/quickstart/
https://www.unison-lang.org/docs/tour/
所感
新しいプログラミング言語の可能性を見れた
AI、Haskell のコードを補完しがち
現状の AI_Agent との親和性があまり高く無いように感じた
ucm の操作を Tools として認識させればいけるか
有志の人が MCP Server を開発している
https://github.com/mizchi/unison-mcp
しかし、IF が AI_Agent で内部で Unison のようなコードベース管理をするような言語というのは面白そう
ハッシュ管理がベストな解決策なのかは分からないが
Q&A
デプロイはどうするの?
Unison Cloud を使うと、ucm 経由で直接デプロイできるみたいです(未調査)
https://www.unison.cloud/learn/http-hello-world/
Docker image が提供されていて 、compile で実行ファイルも生成して ucm run.compiled で実行できるので、AWS など一般的な PaaS でも実行できると思います
学べる教材はあるの?
公式のチュートリアルと exercism があります
https://www.unison-lang.org/docs/quickstart/
https://www.unison-lang.org/docs/at-a-glance/
https://www.unison-lang.org/docs/tour/
https://exercism.org/tracks/unison/
難易度高め: https://www.unison-lang.org/docs/labs/wordle/docs/intro/
Web アプリケーションを開発してみたい場合は Unison Cloud のチュートリアルがあります
https://www.unison.cloud/learn/
どのように開発を進めたら良いの?
公式ドキュメントの以下のページが参考になります
https://www.unison-lang.org/docs/tooling/project-workflows/
実装をいちいち ucm 経由で確認するの面倒じゃない?
デスクトップアプリから update したものに関しては確認できます
https://www.unison-lang.org/docs/tooling/ucm-desktop/
参考
https://www.unison-lang.org/
https://ja.wikibooks.org/wiki/Unison
https://zenn.dev/mizchi/articles/think-next-language-with-unison